/* Copyright (C) 1997-2005, Antony L Hosking.
 * All rights reserved.  */

package Semant;

import java.util.*;

/**
 * Type descriptors for MiniJava.
 */
public abstract class Type {
    /**
     * A type may encapsulate state as fields.
     * 
     * @param n
     *                name of the field
     * @return type of the field
     */
    public Type field(String n) {
        return null;
    }

    /**
     * A type may encapsulate behavior as methods.
     * 
     * @param n
     *                name of the method
     * @return type of the method
     */
    public FUN method(String n) {
        return null;
    }

    /**
     * A type's values may be coerced to those of another type.
     * 
     * @param t
     *                target type
     * @return true if this coerces to t, false otherwise
     */
    public abstract boolean coerceTo(Type t);

    /**
     * Type equality
     * 
     * @param t
     *                target type
     * @return true if types are equivalent, false otherwise
     */
    public abstract boolean equals(Type t);

    /**
     * A type must accept a visitor
     * 
     * @param v
     *                the visitor
     */
    public abstract void accept(Visitor v);

    /**
     * Predefined primitive types
     */
    public static final Primitive BOOLEAN = new Primitive("boolean");
    public static final Primitive INT = new Primitive("int");
    public static final Primitive NIL = new Primitive("<nulltype>");
    public static final Primitive VOID = new Primitive("void");

    static class Primitive extends Type {
        private final String string;

        private Primitive(String s) {
            string = s;
        }

        public boolean equals(Type t) {
            return this == t;
        }

        /**
         * Primitive types coerce if they are equal. The null reference coerces
         * to both array and instance types.
         */
        public boolean coerceTo(Type t) {
            if (this == t)
                return true;
            if (this == NIL)
                return t instanceof ARRAY || t instanceof INSTANCE;
            return false;
        }

        public String toString() {
            return string;
        }

        public void accept(Visitor v) {
            v.visit(this);
        }
    }

    /**
     * A record encapsulates state as fields
     * 
     * @param <T>
     *                type of the fields
     */
    public static class RECORD<T extends Type> extends Type {
        /**
         * The fields are recorded as string-to-type associations in a map.
         */
        public final LinkedHashMap<String, T> map = new LinkedHashMap<String, T>();

        /**
         * Implementation of field for records
         */
        public T field(String n) {
            return map.get(n);
        }

        /**
         * Records coerce if they have the same number of fields and the fields
         * coerce.
         */
        public boolean coerceTo(Type t) {
            if (!(t instanceof RECORD))
                return false;
            Iterator<T> myFields = this.map.values().iterator();
            for (Type field : ((RECORD<?>) t).map.values()) {
                if (!myFields.hasNext())
                    return false;
                Type myField = myFields.next();
                if (!(myField.coerceTo(field)))
                    return false;
            }
            if (myFields.hasNext())
                return false;
            return true;
        }

        /**
         * Records are equal if they have the same number of fields and the
         * fields are equal.
         */
        public boolean equals(Type t) {
            if (!(t instanceof RECORD))
                return false;
            Iterator<T> myFields = this.map.values().iterator();
            for (Type field : ((RECORD<?>) t).map.values()) {
                if (!myFields.hasNext())
                    return false;
                Type myField = myFields.next();
                if (!(myField.equals(field)))
                    return false;
            }
            if (myFields.hasNext())
                return false;
            return true;
        }

        /**
         * A printable representation of this record type.
         */
        public String toString() {
            String result = "RECORD(";
            boolean any = false;
            for (Type f : this.map.values()) {
                if (any)
                    result += ",";
                result += f;
                any = true;
            }
            result += ")";
            return result;
        }

        public void accept(Visitor v) {
            v.visit(this);
        }
    }

    /**
     * A function has formal parameters (represented as a record) and a result
     * type.
     */
    public static class FUN extends Type {
        public final Type result;
        public final RECORD<Type> formals = new RECORD<Type>();
        /**
         * External functions associate with some C library function name.
         */
        public final String external;

        /**
         * A function.
         * 
         * @param r
         *                result type
         */
        public FUN(Type r) {
            result = r;
            external = null;
        }

        /**
         * An external function.
         * 
         * @param r
         *                result type
         * @param x
         *                external function name
         */
        public FUN(Type r, String x) {
            result = r;
            external = x;
        }

        /**
         * Functions coerce if their parameters are equal and their results
         * coerce.
         */
        public boolean coerceTo(Type t) {
            if (!(t instanceof FUN))
                return false;
            FUN fun = (FUN) t;
            return result.coerceTo(fun.result) && formals.equals(fun.formals);
        }

        /**
         * Functions are equal if their parameters are equal and their results
         * are equal.
         */
        public boolean equals(Type t) {
            if (!(t instanceof FUN))
                return false;
            FUN f = (FUN) t;
            return result.equals(f.result) && formals.equals(f.formals);
        }

        /**
         * A printable representation of this function type.
         */
        public String toString() {
            return "FUN(" + formals + "->" + result + ")";
        }

        public void accept(Visitor v) {
            v.visit(this);
        }
    }

    /**
     * An array has an element type.
     */
    public static class ARRAY extends Type {
        public final Type element;

        /**
         * An array.
         * 
         * @param e
         *                element type
         */
        public ARRAY(Type e) {
            element = e;
        }

        /**
         * Java arrays have one field "length".
         */
        public Type field(String n) {
            if (n.equals("length"))
                return Primitive.INT;
            return null;
        }

        /**
         * Arrays coerce if their element types are equal.
         */
        public boolean coerceTo(Type t) {
            if (!(t instanceof ARRAY))
                return false;
            Type e = ((ARRAY) t).element;
            // no array covariance in MJ
            return element.equals(e)
                || ((element instanceof ARRAY) && element.coerceTo(e));
        }

        /**
         * Arrays are equal if their element types are equal.
         */
        public boolean equals(Type t) {
            if (!(t instanceof ARRAY))
                return false;
            Type e = ((ARRAY) t).element;
            return element.equals(e);
        }

        /**
         * A printable representation of this array type.
         */
        public String toString() {
            return element + "[]";
        }

        public void accept(Visitor v) {
            v.visit(this);
        }
    }

    /**
     * A class instance is an instance of some class, encapsulating instance
     * fields and instance methods.
     */
    public static class INSTANCE extends Type {
        public final CLASS instanceOf;

        /**
         * An instance of some class.
         * 
         * @param c
         *                the class
         */
        public INSTANCE(CLASS c) {
            instanceOf = c;
        }

        /**
         * The instance fields.
         */
        public final RECORD<Type> fields = new RECORD<Type>();

        /**
         * Implementation of field for instances. Instances can access both
         * local and inherited instance fields.
         */
        public Type field(String n) {
            Type field;
            // try local instance fields
            if ((field = fields.map.get(n)) != null)
                return field;
            // try inherited instance fields
            if (instanceOf.parent != null
                && (field = instanceOf.parent.instance.field(n)) != null)
                return field;
            return null;
        }

        /**
         * The instance methods.
         */
        public final RECORD<FUN> methods = new RECORD<FUN>();

        /**
         * Implementation of method for instances. Instances can invoke both
         * local and inherited methods.
         */
        public FUN method(String n) {
            FUN method;
            // try local instance methods
            if ((method = methods.map.get(n)) != null)
                return method;
            // try inherited instance methods
            if (instanceOf.parent != null
                && (method = instanceOf.parent.instance.method(n)) != null)
                return method;
            return null;
        }

        /**
         * Instances are equal if they are instances of equal classes.
         */
        public boolean equals(Type t) {
            if (!(t instanceof INSTANCE))
                return false;
            CLASS target = ((INSTANCE) t).instanceOf;
            return instanceOf.equals(target);
        }

        /**
         * Instances coerce if they are instances of equal or parent classes.
         */
        public boolean coerceTo(Type t) {
            if (!(t instanceof INSTANCE))
                return false;
            CLASS target = ((INSTANCE) t).instanceOf;
            for (CLASS c = instanceOf; c != null; c = c.parent)
                if (c.equals(target))
                    return true;
            return false;
        }

        public String toString() {
            return instanceOf.name;
        }

        public void accept(Visitor v) {
            v.visit(this);
        }
    }

    /**
     * A class encapsulates static (class) fields and methods, may extend a
     * parent class, and describes the structure of its instances.
     */
    public static class CLASS extends Type {
        public final String name;
        public CLASS parent = null; // optional
        public final INSTANCE instance = new INSTANCE(this);

        /**
         * A class.
         * 
         * @param n
         *                class name
         */
        public CLASS(String n) {
            name = n;
        }

        /**
         * The static (class) fields.
         */
        public final RECORD<Type> fields = new RECORD<Type>();

        /**
         * Implementation of field for classes. Classes can access both local
         * and inherited static fields.
         */
        public Type field(String n) {
            Type field;
            // try local static fields
            if ((field = fields.map.get(n)) != null)
                return field;
            // try inherited static fields
            if (parent != null && (field = parent.field(n)) != null)
                return field;
            return null;
        }

        /**
         * The static (class) methods.
         */
        public RECORD<FUN> methods = new RECORD<FUN>();

        /**
         * Implementation of method for classes. Classes can access both local
         * and inherited static methods.
         */
        public FUN method(String n) {
            FUN method;
            // try local static methods
            if ((method = methods.map.get(n)) != null)
                return method;
            // try inherited static methods
            if (parent != null && (method = parent.method(n)) != null)
                return method;
            return null;
        }

        /**
         * Flag indicating if class is final.
         */
        public boolean isFinal = false;

        /**
         * Marker to record has been traversed in checking for inheritance
         * cycles.
         */
        private boolean seen = false;

        /**
         * Check for inheritance cycles.
         * 
         * @return true if class forms part of an inheritance cycle.
         */
        public boolean isLoop() {
            if (seen) {
                seen = false;
                return true;
            }
            if (parent != null) {
                seen = true;
                if (parent.isLoop())
                    return true;
                seen = false;
            }
            return false;
        }

        /**
         * Classes are equal only if they are identical.
         */
        public boolean equals(Type t) {
            return this == t;
        }

        /**
         * Classes coerce only if they are identical.
         */
        public boolean coerceTo(Type t) {
            return this == t;
        }

        public String toString() {
            return name + " class";
        }

        public void accept(Visitor v) {
            v.visit(this);
        }
    }

    /**
     * Predefined final classes String and System.
     */
    public static final CLASS STRING = new CLASS("String");
    public static final CLASS SYSTEM = new CLASS("System");
    static {
        STRING.isFinal = true;
        SYSTEM.isFinal = true;
        // System.in is a static field of System, an instance of
        // java.io.InputStream
        CLASS IN = new CLASS("java.io.InputStream");
        SYSTEM.fields.map.put("in", IN.instance);
        // System.out is a static field of System, an instance of
        // java.io.PrintStream
        CLASS OUT = new CLASS("java.io.PrintStream");
        SYSTEM.fields.map.put("out", OUT.instance);
        // System.in has a static read method implemented using C getchar
        IN.methods.map.put("read", new FUN(INT, "getchar"));
        // System.out has a static println method, implemented using C puts
        FUN println = new FUN(VOID, "puts");
        println.formals.map.put("string", STRING.instance);
        OUT.methods.map.put("println", println);
        // System.out has a static write method, implemented using C putchar
        FUN write = new FUN(VOID, "putchar");
        write.formals.map.put("i", INT);
        OUT.methods.map.put("write", write);
    }

    /**
     * The Type Visitor.
     */
    public static interface Visitor {
        void visit(Primitive t);

        void visit(FUN t);

        void visit(ARRAY t);

        <T extends Type> void visit(RECORD<T> t);

        void visit(INSTANCE t);

        void visit(CLASS t);
    }

    /**
     * A visitor to print types.
     */
    public static class Print implements Visitor {
        private final java.io.PrintWriter out;
        private int i = 0;

        public Print(java.io.PrintWriter o) {
            out = o;
        }

        public void visit(Type.ARRAY n) {
            n.element.accept(this);
            say("[]");
        }

        public void visit(Type.Primitive n) {
            say(n);
        }

        public void visit(Type.CLASS n) {
            sayln("CLASS(");
            i += 2;
            indent(i);
            say(n.name);
            sayln(",");
            indent(i);
            if (n.parent == null)
                say("null");
            else
                say(n.parent.name);
            sayln(",");
            n.fields.accept(this);
            sayln(",");
            n.methods.accept(this);
            sayln(",");
            n.instance.accept(this);
            say(")");
            i -= 2;
        }

        public void visit(Type.FUN n) {
            say(n);
        }

        public void visit(Type.INSTANCE n) {
            indent(i);
            sayln("INSTANCE(");
            i += 2;
            n.fields.accept(this);
            sayln(",");
            n.methods.accept(this);
            say(")");
            i -= 2;
        }

        public <T extends Type> void visit(Type.RECORD<T> n) {
            indent(i);
            say("RECORD(");
            i += 2;
            boolean any = false;
            for (Map.Entry<String, T> f : n.map.entrySet()) {
                sayln(any ? "," : "");
                indent(i);
                T value = f.getValue();
                if (value instanceof CLASS)
                    value.accept(this);
                else {
                    say(f.getKey());
                    say(": ");
                    say(f.getValue());
                }
                any = true;
            }
            say(")");
            i -= 2;
        }

        private void indent(int d) {
            for (int i = 0; i < d; i++)
                out.print(' ');
        }

        private void sayln(Object o) {
            say(o);
            out.println();
            out.flush();
        }

        private void say(String s) {
            out.print(s);
        }

        private void say(Object o) {
            say(o.toString());
        }
    }
}
